package com.adv.ems.service.impl;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import com.adv.ems.constants.EmsConst;
import com.adv.ems.db.entity.CollectPointDO;
import com.adv.ems.db.entity.EnergyReportTemplateDO;
import com.adv.ems.db.entity.MeterDO;
import com.adv.ems.enums.DateTypeEnum;
import com.adv.ems.model.dto.BusinessException;
import com.adv.ems.model.dto.ExcelMeterInfo;
import com.adv.ems.model.dto.QueryPointEnergyResultDTO;
import com.adv.ems.model.param.ReportEnergyExcelParam;
import com.adv.ems.model.param.ReportMeterExcelParam;
import com.adv.ems.model.param.ReportTemplateExcelParam;
import com.adv.ems.model.vo.ReportEnergyExcelVO;
import com.adv.ems.model.vo.ReportMeterExcelVO;
import com.adv.ems.model.vo.ReportTemplateExcelVO;
import com.adv.ems.service.ReportService;
import com.adv.ems.utils.ScriptUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class ReportServiceImpl extends BaseServiceImpl implements ReportService {
    @Override
    public ReportEnergyExcelVO energyExcel(ReportEnergyExcelParam param, String acceptLanguage) {
        ReportEnergyExcelVO result = new ReportEnergyExcelVO();
        result.setStandUnite(EmsConst.UNIT_STAND);
        result.setCashUnite(EmsConst.UNIT_CASH);
        for (ReportEnergyExcelParam.Info info: param.getSubjects()) {
            List<Long> pointIds = getPointBySubjectInfo(info.getSubjectType(), info.getSubjectId(), null, null, null, null);
            Date from = DateUtil.parseDateTime(param.getFromDate());
            Date to = DateUtil.parseDateTime(param.getToDate());
            List<QueryPointEnergyResultDTO> energyData = getPointEnergy(param.getDateType(), from, to, pointIds);
            Map<Long, List<QueryPointEnergyResultDTO>> longListMap = groupByMeterId(energyData);
            for (Map.Entry<Long, List<QueryPointEnergyResultDTO>> entry: longListMap.entrySet()) {
                MeterDO meter = meterManager.getAllMap().get(entry.getKey());
                ReportEnergyExcelVO.Info item = new ReportEnergyExcelVO.Info();
                item.setSubjectName(info.getSubjectName());
                item.setMeterCode(meter.getCode());
                item.setMeterName(meter.getName());
                if (meter.getEnergyId() != null && energyInfoManager.getAllMap().get(meter.getEnergyId()) != null) {
                    item.setEnergyUnit(energyInfoManager.getAllMap().get(meter.getEnergyId()).getCumulateUnit());
                }
                double increaseRealValue = 0d;
                double increaseStandValue = 0d;
                double increaseCashValue = 0d;
                double minRealValue = 0d;
                double maxRealValue = 0d;
                for (QueryPointEnergyResultDTO energy: entry.getValue()) {
                    increaseRealValue = increaseRealValue + energy.getIncreaseRealValue();
                    increaseStandValue = increaseStandValue + energy.getIncreaseStandValue();
                    increaseCashValue = increaseCashValue + energy.getIncreaseCashValue();
                    if (minRealValue > energy.getFirstRealValue()) {
                        minRealValue = energy.getFirstRealValue();
                    }
                    if (maxRealValue < energy.getLastRealValue()) {
                        maxRealValue = energy.getLastRealValue();
                    }
                }
                item.setIncreaseRealValue(increaseRealValue);
                item.setIncreaseStandValue(increaseStandValue);
                item.setIncreaseCashValue(increaseCashValue);
                item.setFromValue(minRealValue);
                item.setToValue(maxRealValue);
                result.getList().add(item);
            }
        }
        return result;
    }

    @Override
    public ReportMeterExcelVO deviceExcel(ReportMeterExcelParam param, String acceptLanguage) {
        String userDateFormat = null;
        String dataDateFormat = null;
        Date from = null;
        Date to = null;
        DateTypeEnum sourceDateTypeEnum = null;
        ReportMeterExcelVO result = new ReportMeterExcelVO();
        if (param.getDateType().equals(DateTypeEnum.day)) {
            sourceDateTypeEnum = DateTypeEnum.hour;
            from = DateUtil.beginOfDay(DateUtil.parseDateTime(param.getFromDate()));
            to = DateUtil.beginOfDay(DateUtil.parseDateTime(param.getToDate()));
            userDateFormat = "yyyy-MM-dd";
            dataDateFormat = "HH";
        }else if (param.getDateType().equals(DateTypeEnum.month)) {
            sourceDateTypeEnum = DateTypeEnum.day;
            from = DateUtil.beginOfMonth(DateUtil.parseDateTime(param.getFromDate()));
            to = DateUtil.endOfMonth(DateUtil.parseDateTime(param.getToDate()));
            userDateFormat = "yyyy-MM";
            dataDateFormat = "dd";
        }else {
            sourceDateTypeEnum = DateTypeEnum.month;
            param.setDateType(DateTypeEnum.year);
            from = DateUtil.beginOfYear(DateUtil.parseDateTime(param.getFromDate()));
            to = DateUtil.endOfYear(DateUtil.parseDateTime(param.getToDate()));
            userDateFormat = "yyyy";
            dataDateFormat = "MM";
        }
        result.setStandUnite(EmsConst.UNIT_STAND);
        result.setCashUnite(EmsConst.UNIT_CASH);
        for (ReportMeterExcelParam.Info info: param.getSubjects()) {
            MeterDO meter = meterManager.getAllMap().get(info.getSubjectId());
            if (meter == null) {
                continue;
            }
            ReportMeterExcelVO.Info meterInfo = new ReportMeterExcelVO.Info();
            result.getList().add(meterInfo);
            meterInfo.setSubjectName(info.getSubjectName());
            if (meter.getEnergyId() != null && energyInfoManager.getAllMap().get(meter.getEnergyId()) != null) {
                meterInfo.setEnergyUnit(energyInfoManager.getAllMap().get(meter.getEnergyId()).getCumulateUnit());
            }
            List<Long> pointIds = getCumulatePointIds(ListUtil.of(info.getSubjectId()), null, null, null, null);
            List<QueryPointEnergyResultDTO> pointEnergy = getPointEnergy(sourceDateTypeEnum, from, to, pointIds);
            Map<String, List<ReportMeterExcelVO.Item>> userDateMap = new TreeMap<>();
            for (QueryPointEnergyResultDTO energy: pointEnergy) {
                String userDate = DateUtil.format(energy.getDate(), userDateFormat);
                List<ReportMeterExcelVO.Item> orDefault = userDateMap.getOrDefault(userDate, new ArrayList<>());
                ReportMeterExcelVO.Item item = new ReportMeterExcelVO.Item();
                item.setColDate(DateUtil.format(energy.getDate(), dataDateFormat));
                item.setFromValue(energy.getFirstRealValue());
                item.setToValue(energy.getLastRealValue());
                item.setIncreaseStandValue(energy.getIncreaseStandValue());
                item.setIncreaseCashValue(energy.getIncreaseCashValue());
                item.setIncreaseRealValue(energy.getIncreaseRealValue());
                orDefault.add(item);
                userDateMap.put(userDate, orDefault);
            }
            meterInfo.getDateInfos().stream().collect(Collectors.toMap(d -> d.getUserDate(), d -> d));
            for (Map.Entry<String, List<ReportMeterExcelVO.Item>> entry: userDateMap.entrySet()) {
                String userDate = entry.getKey();
                ReportMeterExcelVO.DateInfo dateInfo = new ReportMeterExcelVO.DateInfo(param.getDateType());
                dateInfo.setUserDate(userDate);
                List<ReportMeterExcelVO.Item> orDefault = entry.getValue();
                for (ReportMeterExcelVO.Item item: orDefault) {
                    ReportMeterExcelVO.Item value = dateInfo.getMap().get(item.getColDate());
                    if (value != null) {
                        BeanUtils.copyProperties(item, value);
                    }
                }
                meterInfo.getDateInfos().add(dateInfo);
            }
        }
        return result;
    }

    @Override
    public ReportTemplateExcelVO templateExcel(ReportTemplateExcelParam param, String acceptLanguage) {
        Optional<EnergyReportTemplateDO> first = energyReportTemplateManager.getAllMap().values().stream().filter(t -> t.getId().equals(param.getReportTemplateId())).findFirst();
        if (!first.isPresent() || first.get().getData() == null) {
            throw new BusinessException("无效模板");
        }
        ByteArrayInputStream inputStream = null;
        ReportTemplateExcelVO result = new ReportTemplateExcelVO();
        try {
            inputStream = new ByteArrayInputStream(first.get().getData());
            ExcelReader reader = ExcelUtil.getReader(inputStream, 0);
            int columnCount = reader.getColumnCount();
            int rowCount = reader.getRowCount();
            Set<ExcelMeterInfo> computeScripts = new HashSet<>();
            for (int r = 0; r < rowCount; r++) {
                List<ReportTemplateExcelVO.Info> rowInfos = new ArrayList<>();
                for (int c = 0; c < columnCount; c++) {
                    Cell cell = reader.getCell(c, r);
                    ReportTemplateExcelVO.Info info = new ReportTemplateExcelVO.Info(r, c, cell);
                    rowInfos.add(info);
                    computeScripts.addAll(ScriptUtil.matchMeters(info.getValue()));
                }
                result.getList().add(rowInfos);
            }
            getMeterData(DateUtil.parseDateTime(param.getFromDate()), DateUtil.parseDateTime(param.getToDate()), computeScripts);
            Sheet sheet = reader.getSheet();
            int numMergedRegions = sheet.getNumMergedRegions();
            for (int i = 0; i < numMergedRegions; i++) {
                CellRangeAddress mergedRegion = sheet.getMergedRegion(i);
                int firstRow = mergedRegion.getFirstRow();
                int lastRow = mergedRegion.getLastRow();
                int firstColumn = mergedRegion.getFirstColumn();
                int lastColumn = mergedRegion.getLastColumn();
                int rowSpan = lastRow - firstRow + 1;
                int colSpan = lastColumn - firstColumn + 1;
                ReportTemplateExcelVO.Info info = result.getList().get(firstRow).get(firstColumn);
                info.setRowspan(rowSpan);
                info.setColspan(colSpan);
            }

            result.getList().forEach(list -> {
                list.forEach(item -> {
                    if (!item.getIsBlank() && StringUtils.hasLength(item.getValue())) {
                        item.setValue(ScriptUtil.computeMeterValue(computeScripts, item.getValue()));
                    }
                });
            });
        }catch (Exception e) {
            log.error("生成数据失败，param【{}】", JSONUtil.toJsonStr(param), e);
            throw new BusinessException("生成数据失败");
        }finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return result;
    }

    @Override
    public void templateExcelDownload(ReportTemplateExcelParam param, HttpServletResponse response) {
        Optional<EnergyReportTemplateDO> first = energyReportTemplateManager.getAllMap().values().stream().filter(t -> t.getId().equals(param.getReportTemplateId())).findFirst();
        if (!first.isPresent() || first.get().getData() == null) {
            throw new BusinessException("无效模板");
        }
        ByteArrayInputStream inputStream = null;
        EnergyReportTemplateDO energyReportTemplateDO = first.get();
        try {
            inputStream = new ByteArrayInputStream(energyReportTemplateDO.getData());
            ExcelReader reader = ExcelUtil.getReader(inputStream, 0);
            int columnCount = reader.getColumnCount();
            int rowCount = reader.getRowCount();
            Set<ExcelMeterInfo> computeScripts = new HashSet<>();
            for (int r = 0; r < rowCount; r++) {
                for (int c = 0; c < columnCount; c++) {
                    Cell cell = reader.getCell(c, r);
                    ReportTemplateExcelVO.Info info = new ReportTemplateExcelVO.Info(r, c, cell);
                    computeScripts.addAll(ScriptUtil.matchMeters(info.getValue()));
                }
            }
            getMeterData(DateUtil.parseDateTime(param.getFromDate()), DateUtil.parseDateTime(param.getToDate()), computeScripts);
            for (int r = 0; r < rowCount; r++) {
                for (int c = 0; c < columnCount; c++) {
                    Cell cell = reader.getCell(c, r);
                    ReportTemplateExcelVO.Info info = new ReportTemplateExcelVO.Info(r, c, cell);
                    if (!info.getIsBlank() && StringUtils.hasLength(info.getValue())) {
                        cell.setCellValue(ScriptUtil.computeMeterValue(computeScripts, info.getValue()));
                    }
                }
            }
            Workbook workbook = reader.getWorkbook();
            response.setHeader("Content-Disposition", "attachment; filename=\"" + energyReportTemplateDO.getFileName() + "\"");
            response.setHeader("Content-Type", "application/octet-stream");
            workbook.write(response.getOutputStream());
        }catch (Exception e) {
            log.error("生成数据失败，param【{}】", JSONUtil.toJsonStr(param), e);
            throw new BusinessException("生成数据失败");
        }finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }


    private void getMeterData(Date fromDate, Date toDate, Set<ExcelMeterInfo> computeMeters) {
        if (CollectionUtils.isEmpty(computeMeters)) {
            return;
        }
        Map<String, List<ExcelMeterInfo>> meterCodeMap = computeMeters.stream().collect(Collectors.groupingBy(ExcelMeterInfo::getMeterCode));
        List<Long> meterIds = meterManager.getAllMap().values().stream().filter(m -> m.getCode() != null && meterCodeMap.containsKey(m.getCode())).map(MeterDO::getId).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(meterIds)) {
            return;
        }
        List<Long> pointIds = getCumulatePointIds(meterIds, null, null, null, null);
        if (CollectionUtils.isEmpty(pointIds)) {
            return;
        }
        List<QueryPointEnergyResultDTO> pointEnergy = getPointEnergy(DateTypeEnum.day, fromDate, toDate, pointIds);
        Map<Long, List<QueryPointEnergyResultDTO>> groupByPoint = pointEnergy
                .stream()
                .sorted(Comparator.comparing(QueryPointEnergyResultDTO :: getDate))
                .collect(Collectors.groupingBy(QueryPointEnergyResultDTO::getPointId));
        for (Map.Entry<Long, List<QueryPointEnergyResultDTO>> entry : groupByPoint.entrySet()) {
            Double firstVal = null;
            Double lastVal = null;
            Long pointId = entry.getKey();
            CollectPointDO pointDO = collectPointManager.getAllMap().get(pointId);
            MeterDO meterDO = meterManager.getAllMap().get(pointDO.getMeterId());
            Double increaseVal = 0d;
            for (QueryPointEnergyResultDTO energy: pointEnergy) {
                if (firstVal == null) {
                    firstVal = energy.getFirstRealValue();
                }
                lastVal = energy.getLastRealValue();
                increaseVal = increaseVal + energy.getIncreaseRealValue();
            }
            for (ExcelMeterInfo excelMeterInfo: meterCodeMap.get(meterDO.getCode())) {
                excelMeterInfo.setValue(increaseVal);
                excelMeterInfo.setFirstValue(firstVal);
                excelMeterInfo.setLastValue(lastVal);
            }
        }
    }

    private Map<Long, List<QueryPointEnergyResultDTO>> groupByMeterId(List<QueryPointEnergyResultDTO> energyData) {
        Map<Long, List<QueryPointEnergyResultDTO>> groupByMeterId = new LinkedHashMap<>();
        for (QueryPointEnergyResultDTO data: energyData) {
            CollectPointDO pointDO = collectPointManager.getAllMap().get(data.getPointId());
            if (pointDO == null || pointDO.getMeterId() == null) {
                continue;
            }
            MeterDO meter = meterManager.getAllMap().get(pointDO.getMeterId());
            if (meter == null) {
                continue;
            }
            List<QueryPointEnergyResultDTO> orDefault = groupByMeterId.getOrDefault(meter.getId(), new ArrayList<>());
            orDefault.add(data);
            groupByMeterId.put(meter.getId(), orDefault);
        }
        return groupByMeterId;
    }
}
